<?php
#
# dmBridge: a data access framework for CONTENTdm(R)
#
# Copyright © 2009, 2010, 2011 Board of Regents of the Nevada System of Higher
# Education, on behalf of the University of Nevada, Las Vegas
#

/**
 * <p>Encapsulates an RFC 2396 Uniform Resource Identifier (URI), a superset
 * of a URL.</p>
 * 
 * <p>GET data is available via the getQuery() methods. Note that unlike the
 * PHP <code>$_GET</code> superglobal, getQuery() supports multiple
 * identically named keys.</p>
 *
 * @author Alex Dolski <alex.dolski@unlv.edu>
 * @license http://www.opensource.org/licenses/mit-license.php
 */
class DMURI {

	/**
	 * @var string
	 */
	private $fragment;

	/**
	 * @var string
	 */
	private $host;

	/**
	 * @var string
	 */
	private $password;

	/**
	 * @var string
	 */
	private $path = "/";

	/**
	 * @var int
	 */
	private $port = 80;

	/**
	 * @var array of arrays with "key" and "value" keys
	 */
	protected $query = array();

	/**
	 * @var string
	 */
	private $scheme = "http";

	/**
	 * @var string
	 */
	private $user;

	/**
	 * @param string path
	 * @return DMURI
	 */
	public static function getLocalURIWithPath($path) {
		$uri = new DMURI(DMHTTPRequest::getCurrent()->getURI());
		$uri->setPath($path);
		return $uri;
	}

	/**
	 * @param string uri
	 * @throws DMIllegalArgumentException if $uri is not a URI
	 */
	public function __construct($uri = null) {
		if ($uri) {
			$this->setString($uri);
		}
	}

	/**
	 * @return string The string representation of the URI, or null if the URI
	 * is invalid.
	 */
	public function __toString() {
		return (string) $this->getAbsoluteURIAsString();
	}

	/**
	 * @param object obj
	 * @return boolean
	 */
	public function equals($obj) {
		if (!$obj instanceof DMURI) {
			return false;
		}
		return ($obj->__toString() == $this->__toString());
	}

	/**
	 * @return string The string representation of the URI, or null if the URI
	 * is invalid.
	 */
	public function getAbsoluteURIAsString() {
		if (!$this->getScheme() || !$this->getHost()) {
			return null;
		}

		$uri = $this->getScheme() . "://";
		if ($this->getUser()) {
			$uri .= $this->getUser();
		}
		if ($this->getPassword()) {
			$uri .= ":" . $this->getPassword();
		}
		if ($this->getUser() || $this->getPassword()) {
			$uri .= "@";
		}
		$uri .= $this->getHost();
		if ($this->getPort() != 80) {
			$uri .= ":" . $this->getPort();
		}
		$uri .= $this->getPath();
		if (count($this->getQuery())) {
			$uri .= "?" . $this->getQueryString();
		}
		if ($this->getFragment()) {
			$uri .= "#" . $this->getFragment();
		}
		return $uri;
	}

	/**
	 * The absolute URI of the host, including only scheme, host, and port.
	 *
	 * @return DMURI
	 */
	public function getAbsoluteHostURI() {
		$uri = $this->getScheme() . "://";
		$uri .= $this->getHost();
		if ($this->getPort() != 80) {
			$uri .= ":" . $this->getPort();
		}
		return new DMURI($uri);
	}

	/**
	 * @return string The fragment component of the URI
	 */
	public function getFragment() {
		return $this->fragment;
	}

	/**
	 * @param string fragment
	 */
	public function setFragment($fragment) {
		$this->fragment = $fragment;
	}

	/**
	 * @return string
	 */
	public function getHost() {
		return $this->host;
	}

	/**
	 * @param string host The host part of the URI
	 */
	public function setHost($host) {
		$this->host = $host;
	}

	/**
	 * @return string
	 */
	public function getPassword() {
		return $this->password;
	}

	/**
	 * @param string password The password part of the URI
	 */
	public function setPassword($password) {
		$this->password = $password;
	}

	/**
	 * @return string
	 */
	public function getPath() {
		return $this->path;
	}

	/**
	 * @param string path The path part of the URI
	 */
	public function setPath($path) {
		$this->path = $path;
	}

	/**
	 * @return array Array of path components
	 */
	public function getPathComponents() {
		return explode("/", trim($this->path, "/"));
	}

	/**
	 * @return int
	 */
	public function getPort() {
		return $this->port;
	}

	/**
	 * @param int port The TCP port part of the URI
	 */
	public function setPort($port) {
		$this->port = (int) $port;
	}

	/**
	 * Adds the given key/value pair to the query. Multiple identical keys can
	 * be added. Null values will not be added.
	 *
	 * @param string key Unencoded key
	 * @param string value Unencoded value
	 * @see setQueryValue()
	 */
	public function addQueryValue($key, $value) {
		if (!is_null($value)) {
			$this->query[] = array(
				'key' => (string) $key,
				'value' => (string) $value
			);
		}
	}

	/**
	 * @return array Array of arrays, each with "key" and "value" keys. This is
	 * not a simple one-dimensional associative array because a URI query may
	 * have multiple identically-named keys.
	 * @see getQueryValue()
	 * @see getQueryValues()
	 */
	public function getQuery() {
		return $this->query;
	}

	/**
	 * @return string The encoded URI query string, without preceding
	 * question mark
	 */
	public function getQueryString() {
		$query = array();
		foreach ($this->query as $var) {
			$query[] = urlencode($var['key']) . "=" . urlencode($var['value']);
		}
		return implode("&", $query);
	}

	/**
	 * @param string str Non-URL-encoded query string
	 */
	public function setQueryString($str) {
		$pairs = explode("&", $str);
		foreach ($pairs as $pair) {
			$kv = explode("=", $pair);
			if (count($kv) == 2) {
				$this->addQueryValue($kv[0], $kv[1]);
			}
		}
	}

	/**
	 * Unsets all key/value pairs whose key matches $key, and adds the given
	 * key/value pair to the query.
	 * 
	 * @param string key Unencoded key
	 * @param string value Unencoded value
	 * @see addQueryVariable()
	 */
	public function setQueryValue($key, $value) {
		$this->unsetQueryKey($key);
		$this->addQueryValue($key, $value);
	}

	/**
	 * @return string
	 */
	public function getScheme() {
		return $this->scheme;
	}

	/**
	 * @param string scheme The scheme part of the URI, e.g. "http"
	 */
	public function setScheme($scheme) {
		$this->scheme = $scheme;
	}

	/**
	 * @param string str
	 * @throws DMIllegalArgumentException if $str is not a valid URI
	 */
	public function setString($str) {
		$tmp = parse_url($str);

		if (!is_array($tmp)) {
			throw new DMIllegalArgumentException(
					DMLocalizedString::getString("INVALID_URL"));
		}

		if (array_key_exists("scheme", $tmp)) {
			$this->setScheme($tmp['scheme']);
		}
		if (array_key_exists("host", $tmp)) {
			$this->setHost($tmp['host']);
		}
		if (array_key_exists("port", $tmp)) {
			$this->setPort($tmp['port']);
		}
		if (array_key_exists("user", $tmp)) {
			$this->setUser($tmp['user']);
		}
		if (array_key_exists("pass", $tmp)) {
			$this->setPassword($tmp['pass']);
		}
		if (array_key_exists("path", $tmp)) {
			$this->setPath($tmp['path']);
		}
		$this->unsetQuery();
		if (array_key_exists("query", $tmp)) {
			$q = explode("&", $tmp['query']);
			foreach ($q as $kv) {
				$parts = explode("=", $kv);
				if (count($parts) == 2) {
					$this->addQueryValue(
							urldecode($parts[0]), urldecode($parts[1]));
				}
			}
		}
		if (array_key_exists("fragment", $tmp)) {
			$this->setFragment($tmp['fragment']);
		}
	}

	/**
	 * @return string
	 */
	public function getUser() {
		return $this->user;
	}

	/**
	 * @param string user The user part of the URI (optional)
	 */
	public function setUser($user) {
		$this->user = $user;
	}

	/**
	 * @return boolean
	 */
	public function isValid() {
		$valid = true;
		if (!filter_var($this->__toString(), FILTER_VALIDATE_URL,
				FILTER_FLAG_SCHEME_REQUIRED)) {
			$valid = false;
		}
		if (!$this->getScheme()) {
			$valid = false;
		}
		if ($this->getPassword() && !$this->getUser()) {
			$valid = false;
		}
		return $valid;
	}

	/**
	 * @param string key
	 * @return string The value of the first query whose key matches $key.
	 * @see getQuery()
	 * @see getQueryValues()
	 */
	public function getQueryValue($key) {
		foreach ($this->query as $q) {
			if ($q['key'] == $key) {
				return $q['value'];
			}
		}
		return null;
	}

	/**
	 * @param string key
	 * @return array Array of values for every key matching $key.
	 * @see getQuery()
	 * @see getQueryValue()
	 */
	public function getQueryValues($key) {
		$values = array();
		foreach ($this->query as $q) {
			if ($q['key'] == $key) {
				$values[] = $q['value'];
			}
		}
		return $values;
	}

	/**
	 * Erases the query.
	 */
	public function unsetQuery() {
		$this->query = array();
	}

	/**
	 * Unsets all query variables having a key of $key.
	 *
	 * @param string key
	 */
	public function unsetQueryKey($key) {
		$count = count($this->query);
		for ($i = 0; $i < $count; $i++) {
			if (array_key_exists($i, $this->query)
					&& $this->query[$i]['key'] == $key) {
				unset($this->query[$i]);
			}
		}
	}

}
